package com.one.blocks.security.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.one.blocks.security.authc.RestAuthenticationEntryPoint;
import com.one.blocks.security.authc.RestAuthenticationFailureHandler;
import com.one.blocks.security.authc.RestLoginSuccessHandler;
import com.one.blocks.security.authc.logout.RestLogoutHandler;
import com.one.blocks.security.authc.logout.RestLogoutSuccessHandler;
import com.one.blocks.security.authc.password.PasswordAuthenticationProcessingFilter;
import com.one.blocks.security.authc.password.PasswordAuthenticationProvider;
import com.one.blocks.security.authc.rememberme.RememberMeFilter;
import com.one.blocks.security.authc.rememberme.RememberMeTokenFilter;
import com.one.blocks.security.authc.rememberme.TokenService;
import com.one.blocks.security.authc.smscode.SmsCodeAuthenticationProcessingFilter;
import com.one.blocks.security.authc.smscode.SmsCodeAuthenticationProvider;
import com.one.blocks.security.authc.smscode.SmsCodeService;
import com.one.blocks.security.authz.AuthorizeAspect;
import com.one.blocks.security.authz.AuthorizeService;
import com.one.blocks.security.authz.RestAccessDeniedHandler;
import com.one.blocks.security.config.properties.SecurityProperties;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import java.util.List;

/**
 * @author <a href="mailto:idler41@163.con">linfuxin</a> created on 2023-09-24 10:59:53
 */
@Configuration
@EnableConfigurationProperties(SecurityProperties.class)
@AutoConfigureAfter(JacksonAutoConfiguration.class)
@Import({DefaultWebSecurityConfig.class})
public class SecurityAutoConfig {

    @Bean
    @ConditionalOnBean(AuthorizeService.class)
    public AuthorizeAspect authorizeAspect(AuthorizeService authorizeService) {
        return new AuthorizeAspect(authorizeService);
    }

    @Bean
    @ConditionalOnMissingBean
    public RestLoginSuccessHandler restLoginSuccessHandler(TokenService accessService, ObjectMapper objectMapper) {
        return new RestLoginSuccessHandler(accessService, objectMapper);
    }

    @Bean
    @ConditionalOnMissingBean
    public RestAuthenticationFailureHandler restAuthenticationFailureHandler(ObjectMapper objectMapper) {
        return new RestAuthenticationFailureHandler(objectMapper);
    }

    @Bean
    public AuthenticationManager authenticationManager(List<AuthenticationProvider> authenticationProviderList) {
        return new ProviderManager(authenticationProviderList);
    }

    @Bean
    @ConditionalOnMissingBean
    public PasswordEncoder passwordEncoder() {
        DelegatingPasswordEncoder passwordEncoder = (DelegatingPasswordEncoder) PasswordEncoderFactories.createDelegatingPasswordEncoder();
        passwordEncoder.setDefaultPasswordEncoderForMatches(new BCryptPasswordEncoder(4));
        return passwordEncoder;
    }

    @Bean
    @ConditionalOnMissingBean(PasswordAuthenticationProvider.class)
    @ConditionalOnBean({PasswordEncoder.class, UserDetailsService.class})
    public PasswordAuthenticationProvider passwordAuthenticationProvider(PasswordEncoder passwordEncoder, UserDetailsService userDetailsService, SecurityProperties securityProperties) {
        return new PasswordAuthenticationProvider(passwordEncoder, userDetailsService, securityProperties);
    }

    @Bean
    @ConditionalOnProperty(prefix = "app.blocks.security.form", name = "username-enable", havingValue = "true", matchIfMissing = true)
    public PasswordAuthenticationProcessingFilter passwordAuthenticationProcessingFilter(
            SecurityProperties props, AuthenticationManager authenticationManager, ObjectMapper objectMapper,
            RestLoginSuccessHandler loginSuccessHandler, RestAuthenticationFailureHandler restAuthenticationFailureHandler
    ) {
        PasswordAuthenticationProcessingFilter filter = new PasswordAuthenticationProcessingFilter(
                props.getLoginUrl(), props.getForm().getUsername(), props.getForm().getPassword(),
                authenticationManager, loginSuccessHandler, restAuthenticationFailureHandler, objectMapper);
        return filter;
    }

    @Bean
    @ConditionalOnProperty(prefix = "app.blocks.security.form", name = "smsCode-enable", havingValue = "true")
    public SmsCodeAuthenticationProcessingFilter smsCodeAuthenticationProcessingFilter(
            SecurityProperties props, AuthenticationManager authenticationManager,
            RestLoginSuccessHandler loginSuccessHandler, RestAuthenticationFailureHandler restAuthenticationFailureHandler
    ) {
        return new SmsCodeAuthenticationProcessingFilter(
                props.getLoginUrl(), props.getForm().getMobile(), props.getForm().getSmsCode(),
                authenticationManager, loginSuccessHandler, restAuthenticationFailureHandler);
    }

    @Bean
    @ConditionalOnMissingBean(RememberMeFilter.class)
    @ConditionalOnBean(TokenService.class)
    public RememberMeFilter rememberMeFilter(TokenService tokenService,SecurityProperties props) {
        return new RememberMeTokenFilter(tokenService, props);
    }

    @Bean
    @ConditionalOnBean({SmsCodeService.class, SmsCodeAuthenticationProcessingFilter.class})
    public SmsCodeAuthenticationProvider smsAuthenticationProvider(SmsCodeService smsCodeService) {
        return new SmsCodeAuthenticationProvider(smsCodeService);
    }

    @Bean
    @ConditionalOnMissingBean(AuthenticationEntryPoint.class)
    public RestAuthenticationEntryPoint authenticationEntryPoint(ObjectMapper objectMapper) {
        return new RestAuthenticationEntryPoint(objectMapper);
    }

    @Bean
    @ConditionalOnMissingBean(AccessDeniedHandler.class)
    public RestAccessDeniedHandler accessDeniedHandler(ObjectMapper objectMapper) {
        return new RestAccessDeniedHandler(objectMapper);
    }

    @Bean
    @ConditionalOnMissingBean(LogoutHandler.class)
    public LogoutHandler logoutHandler(TokenService tokenService) {
        return new RestLogoutHandler(tokenService);
    }

    @Bean
    @ConditionalOnMissingBean(LogoutSuccessHandler.class)
    public LogoutSuccessHandler logoutSuccessHandler(ObjectMapper objectMapper) {
        return new RestLogoutSuccessHandler(objectMapper);
    }
}
